package com.evan.webadmin.biz.service.impl;

import cn.hutool.json.JSONUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.util.ListUtils;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.evan.common.core.constant.*;
import com.evan.common.core.exception.MyRuntimeException;
import com.evan.common.core.object.ResponseResult;
import com.evan.webadmin.biz.dao.SubjectMapper;
import com.evan.webadmin.biz.dto.*;
import com.evan.webadmin.biz.helper.DictHelper;
import com.evan.webadmin.biz.model.*;
import com.evan.webadmin.biz.service.*;
import com.evan.webadmin.biz.vo.EvluateSurveyVO;
import com.evan.webadmin.biz.vo.ExamPaperFrontVO;
import com.evan.webadmin.biz.vo.SubjectFrontVo;
import com.evan.webadmin.biz.vo.SubjectOptionFrontVo;
import jodd.util.StringPool;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author Pavel
 * @since 2024-06-09
 */
@Service
@Slf4j
public class SubjectServiceImpl extends ServiceImpl<SubjectMapper, Subject> implements ISubjectService {

    @Autowired
    SubjectMapper subjectMapper;
    @Autowired
    ISubjectOptionService optionService;
    @Autowired
    DictHelper dictHelper;
    @Autowired
    IExamPaperService examPaperService;
    @Autowired
    IExamPaperDetailService examPaperDetailService;
    @Autowired
    IEvluateSurveyService surveyService;
    @Autowired
    IEvluateSurveyDetailService surveyDetailService;

    /**
     * 单次缓存的数据量
     */
    public static final int BATCH_COUNT = 100;
    /**
     *临时存储
     */
    private List<SubjectImportDto> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);

    @Override
    public List<Subject> findByCondition(Integer personnelType, Integer moduleType, Integer subjectType, Integer sceneType, String subjectTitle, Integer subjectLevel) {
        QueryWrapper<Subject> wrapper = new QueryWrapper<Subject>();
        if (StringUtils.isNotBlank(subjectTitle)){
            wrapper.like("subject_title", subjectTitle);
        }
        if(personnelType != null && personnelType != PersonnelTypeEnum.DEFAULT.getCode()){
            wrapper.eq("personnel_type", personnelType);
        }
        if(moduleType != null && moduleType != ModuleTypeEnum.DEFAULT.getCode()){
            wrapper.eq("module_type", moduleType);
        }
        if(sceneType != null){
            wrapper.eq("scene_type", sceneType);
        }
        if(subjectType != null){
            wrapper.eq("subject_type", subjectType);
        }
        if(subjectLevel != null){
            wrapper.eq("subject_level", subjectLevel);
        }
        wrapper.eq("delete_flag", 0L);
        wrapper.orderByDesc("create_time");
        return subjectMapper.selectList(wrapper);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void edit(SubjectEditDto subjectEditDto) {
        //保存或更新题目
        Subject subject ;
        if(subjectEditDto.getSubjectId() != null){
            subject = subjectMapper.selectBySubjectId(subjectEditDto.getSubjectId());
            subject.setUpdateTime(new Date());
        }else{
            subject = new Subject();
            subject.setCreateTime(new Date());
        }
        subject.setSubjectTitle(subjectEditDto.getSubjectTitle());
        if(subjectEditDto.getModuleType() != ModuleTypeEnum.EXAM.getCode()){
            subject.setPersonnelType(ModuleTypeEnum.DEFAULT.getCode());
        }else{
            subject.setPersonnelType(subjectEditDto.getPersonnelType());
        }
        if(subjectEditDto.getModuleType() == ModuleTypeEnum.EVLUATE.getCode()){
            subject.setSubjectType(SubjectTypeEnum.SURVEY.getCode());
        }else{
            subject.setSubjectType(subjectEditDto.getSubjectType());
        }
        subject.setModuleType(subjectEditDto.getModuleType());
        if(subjectEditDto.getModuleType() == ModuleTypeEnum.COURSE.getCode()){
            subject.setSceneType(subjectEditDto.getSceneType());
        }

        if(subject.getSubjectType() == SubjectTypeEnum.MULTI_SELECT.getCode()){
            subject.setAnswer(subjectEditDto.getCorrectArray().stream().map(String::valueOf).collect(Collectors.joining("/")));
        }else{
            subject.setAnswer(subjectEditDto.getAnswer());
        }
        subject.setSubjectScore(subjectEditDto.getSubjectScore());
        subject.setSubjectLevel(subjectEditDto.getSubjectLevel());
        subject.setTextAnswer(subjectEditDto.getTextAnswer());
        subject.setAnswerAnalysis(subjectEditDto.getAnswerAnalysis());
        subject.setTitleAnalysis(subjectEditDto.getTitleAnalysis());

        if(subject.getSubjectId() != null){
            subjectMapper.update(subject, new QueryWrapper<Subject>().eq("subject_id", subject.getSubjectId()));
        }else{
            subjectMapper.insert(subject);
        }
        //保存选项 更新题目答案
        List<SubjectOption> options = new ArrayList<>();
        for (SubjectOptionEditDto optionEditDto: subjectEditDto.getOptions()) {
            SubjectOption option = new SubjectOption();
            option.setOptionContent(optionEditDto.getOptionContent());
            option.setOptionTitle(optionEditDto.getOptionTitle());
            option.setCorrectAble(optionEditDto.getCorrectAble());
            option.setSubjectId(subject.getSubjectId());
            options.add(option);
        }
        optionService.deleteBySubjectId(subject.getSubjectId());
        optionService.saveBatch(options);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delete(Long subjectId) {
        Subject exist = subjectMapper.selectBySubjectId(subjectId);
        if(exist != null){
            optionService.deleteBySubjectId(subjectId);
            subjectMapper.deleteById(subjectId);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResponseResult importData(MultipartFile file, Integer personnelType, Integer moduleType) {
        if (file == null || file.isEmpty()) {
            return ResponseResult.error("", "文件不能为空");
        }
        try {
            EasyExcel.read(file.getInputStream(), SubjectImportDto.class, new ReadListener<SubjectImportDto>() {
                @Override
                public void invoke(SubjectImportDto data, AnalysisContext context) {
                    if(cachedDataList.size() >= 1){
                        if(StringUtils.isBlank(data.getScene())){
                            data.setScene(cachedDataList.get(cachedDataList.size() - 1).getScene());
                        }
                    }
                    cachedDataList.add(data);
                    if (cachedDataList.size() >= BATCH_COUNT) {
                        saveData(personnelType, moduleType);
                        // 存储完成清理 list
                        cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
                    }
                }

                @Override
                public void doAfterAllAnalysed(AnalysisContext context) {
                    saveData(personnelType, moduleType);
                    cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
                }
            }).sheet().doRead();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
        }
        return ResponseResult.success();
    }

    @Override
    public Subject findById(Long subjectId) {
        Subject subject = subjectMapper.selectBySubjectId(subjectId);
        List<SubjectOption> options = optionService.findBySubjectId(subjectId);
        subject.setOptions(options);
        return subject;
    }

    @Override
    public ExamPaperFrontVO findByFront(SubjectFilter subjectFilter) {
        ExamPaperFrontVO examPaperFrontVO = new ExamPaperFrontVO();
        QueryWrapper wrapper = new QueryWrapper();
        List<Subject> subjects = new ArrayList<>();
        //闯关模式 固定
        if(subjectFilter.getModuleType() == ModuleTypeEnum.COURSE.getCode()){
            wrapper.eq("module_type", ModuleTypeEnum.COURSE.getCode());
            wrapper.eq("scene_type", subjectFilter.getSceneType());
            wrapper.eq("delete_flag", 0L);
            wrapper.eq("subject_level", subjectFilter.getSubjectLevel());
            subjects = subjectMapper.selectList(wrapper);
        }else if(subjectFilter.getModuleType() == ModuleTypeEnum.EXAM.getCode()){
            //练习学员考试 随机10题
            if(ExamModeEnum.PRACTICE.getType() == subjectFilter.getExamMode()){
                wrapper.eq("module_type", ModuleTypeEnum.EXAM.getCode());
                wrapper.eq("personnel_type", subjectFilter.getPersonnelType());
                wrapper.eq("delete_flag", 0L);
                subjects = subjectMapper.selectList(wrapper);
                if(subjects != null && subjects.size() > 20){
                    subjects = randomSubjects(subjects, 20);
                }
            }else if(ExamModeEnum.EXAM.getType() == subjectFilter.getExamMode()){
                ExamPaper paper = examPaperService.findPublishedPaper(subjectFilter.getPersonnelType(), subjectFilter.getModuleType());
                if(paper == null){
                    throw new MyRuntimeException("不存在已发布的试卷，请练习管理员");
                }else{
                    List<ExamPaperDetail> details = examPaperDetailService.detail(paper.getPaperId());
                    List<SubjectFrontVo> subjectFrontVos = new ArrayList<>();
                    details.forEach(item -> {
                        subjectFrontVos.add(JSONUtil.toBean(JSONUtil.toJsonStr(item.getSubjectContent()), SubjectFrontVo.class));
                    });
                    examPaperFrontVO.setPaperId(paper.getPaperId());
                    examPaperFrontVO.setPaperName(paper.getPaperName());
                    examPaperFrontVO.setSubjects(subjectFrontVos);
                    return examPaperFrontVO;
                }
            }
        }
        examPaperFrontVO.setSubjects(assemblyFront(subjects));
        return examPaperFrontVO;
    }

    @Override
    public List<Subject> list(Integer personnelType, Integer moduleType, Integer sceneType) {
        QueryWrapper wrapper = new QueryWrapper();
        if(personnelType != null && ModuleTypeEnum.EXAM.getCode() == moduleType){
            wrapper.eq("personnel_type", personnelType);
        }
        if(moduleType != null){
            wrapper.eq("module_type", moduleType);
        }
        if(sceneType != null && ModuleTypeEnum.COURSE.getCode() == moduleType){
            wrapper.eq("scene_type", sceneType);
        }
        return subjectMapper.selectList(wrapper);
    }

    public List<SubjectFrontVo> assemblyFront(List<Subject> subjects) {
        List<SubjectFrontVo> result = new ArrayList<>();
        if(CollectionUtils.isEmpty(subjects)){
            return result;
        }
        for (Subject subject : subjects) {
            SubjectFrontVo frontVo = new SubjectFrontVo();
            frontVo.setSubjectId(subject.getSubjectId());
            frontVo.setSubjectTitle(subject.getSubjectTitle());
            frontVo.setSubjectType(subject.getSubjectType());
            frontVo.setSubject(dictHelper.getDictMap(BizConstant.DICTTYPE_SUBJECT_CODE).get(subject.getSubjectType() + ""));
            frontVo.setPersonnel(dictHelper.getDictMap(BizConstant.DICTTYPE_PERSONNEL_CODE).get(subject.getPersonnelType() + ""));
            frontVo.setSubjectScore(subject.getSubjectScore());
            List<SubjectOption> options = optionService.findBySubjectId(subject.getSubjectId());
            frontVo.setOptions(assemblyOptionFront(options));
            result.add(frontVo);
        }
        return result;
    }

    private  List<SubjectOptionFrontVo> assemblyOptionFront(List<SubjectOption> options) {
        List<SubjectOptionFrontVo> result = new ArrayList<>();
        if(CollectionUtils.isEmpty(options)){
            return result;
        }
        for (SubjectOption option : options) {
            SubjectOptionFrontVo frontVo = new SubjectOptionFrontVo();
            frontVo.setOptionId(option.getOptionId());
            frontVo.setOptionTitle(option.getOptionTitle());
            frontVo.setOptionContent(option.getOptionContent());
            frontVo.setSubjectId(option.getSubjectId());
            result.add(frontVo);
        }
        return result;
    }

    private List<Subject> randomSubjects(List<Subject> subjects, Integer count){
        List<Subject> result = new ArrayList<>();
        if(CollectionUtils.isEmpty(subjects)){
            return result;
        }
        if(count > subjects.size()){
            count = subjects.size();
        }
        Random random = new Random();
        for(int i = 0; i < count; i++){
            int index = random.nextInt(subjects.size());
            result.add(subjects.get(index));
            subjects.remove(index);
        }
        return result;
    }

    private void saveData(Integer personnelType, Integer moduleType) {
        log.info("{}条数据，开始存储数据库！", cachedDataList.size());
        List<Subject> subjects = new ArrayList<>();
        for (SubjectImportDto importDto : cachedDataList) {
            Subject subject = new Subject();
            if(moduleType == ModuleTypeEnum.EXAM.getCode()){
                subject.setPersonnelType(personnelType);
            }else{
                subject.setPersonnelType(PersonnelTypeEnum.DEFAULT.getCode());
            }
            subject.setModuleType(moduleType);
            subject.setSubjectLevel(0);
            if(ModuleTypeEnum.COURSE.getCode() == moduleType && StringUtils.isNotBlank(importDto.getScene())){
                String res = dictHelper.getDictNameMap(BizConstant.DICTTYPE_SCENE_CODE).get(importDto.getScene());
                if(res != null){
                    int sceneType = Integer.valueOf(res);
                    subject.setSceneType(sceneType);
                }else{
                 log.warn("{} import challenge subject scene type not exist", importDto.getScene());
                }
                if(StringUtils.isNotBlank(importDto.getSubjectLevel())){
                    String level = dictHelper.getDictNameMap(BizConstant.DICTTYPE_SUBJECT_LEVEL_CODE).get(importDto.getSubjectLevel());
                    if(level != null){
                        int levelType = Integer.valueOf(level);
                        subject.setSubjectLevel(levelType);
                    }else{
                        log.warn("{} import challenge subject level type not exist", importDto.getScene());
                    }
                }
            }
            subject.setSubjectType(SubjectTypeEnum.getByName(importDto.getSubjectType()).getCode());
            subject.setSubjectTitle(importDto.getSubjectContent());
            subject.setSubjectScore(StringUtils.isNotBlank(importDto.getSubjectScore())?Integer.parseInt(importDto.getSubjectScore()):3);
            subject.setTitleAnalysis(importDto.getTitleAnalysis());
            subject.setAnswerAnalysis(importDto.getAnswerAnalysis());
            subject.setCreateTime(new Date());
            if(SubjectTypeEnum.JUDGE.getCode() == subject.getSubjectType()){
                try{
                    subject.setAnswer(convertToLetter(Integer.valueOf(importDto.getAnswer())));
                }catch (NumberFormatException e){
                    log.warn("{}-{} import challenge subject answer format error",importDto.getSubjectContent(), importDto.getAnswer());
                    continue;
                }
            } else if (SubjectTypeEnum.FILL_IN.getCode() == subject.getSubjectType()) {
                subject.setTextAnswer(importDto.getTextAnswer());
            } else{
                subject.setAnswer(numbers2Letters(importDto.getAnswer()));
            }
            subjects.add(subject);
        }
        this.saveBatch(subjects);

        List<SubjectOption> options = new ArrayList<>();
        for (int i = 0; i < subjects.size(); i++)  {
            Subject subject = subjects.get(i);
            if(SubjectTypeEnum.FILL_IN.getCode() == subject.getSubjectType()){
                continue;
            }
            SubjectImportDto importDto = cachedDataList.get(i);
            if(StringUtils.isNotBlank(importDto.getOption_1())){
                SubjectOption option1 = new SubjectOption();
                option1.setCreateTime(new Date());
                option1.setOptionTitle("A");
                option1.setOptionContent(importDto.getOption_1());
                option1.setCorrectAble("A".indexOf(subject.getAnswer()) >= 0);
                option1.setSubjectId(subject.getSubjectId());
                options.add(option1);
            }

            if(StringUtils.isNotBlank(importDto.getOption_2())){
                SubjectOption option2 = new SubjectOption();
                option2.setOptionTitle("B");
                option2.setCreateTime(new Date());
                option2.setOptionContent(importDto.getOption_2());
                option2.setCorrectAble("B".indexOf(subject.getAnswer()) >= 0);
                option2.setSubjectId(subject.getSubjectId());
                options.add(option2);
            }

            if(StringUtils.isNotBlank(importDto.getOption_3())){
                SubjectOption option3 = new SubjectOption();
                option3.setCreateTime(new Date());
                option3.setOptionTitle("C");
                option3.setOptionContent(importDto.getOption_3());
                option3.setCorrectAble("C".indexOf(subject.getAnswer()) >= 0);
                option3.setSubjectId(subject.getSubjectId());
                options.add(option3);
            }

            if(StringUtils.isNotBlank(importDto.getOption_4())){
                SubjectOption option4 = new SubjectOption();
                option4.setCreateTime(new Date());
                option4.setOptionTitle("D");
                option4.setOptionContent(importDto.getOption_4());
                option4.setCorrectAble("D".indexOf(subject.getAnswer()) >= 0);
                option4.setSubjectId(subject.getSubjectId());
                options.add(option4);
            }

            if(StringUtils.isNotBlank(importDto.getOption_5())){
                SubjectOption option5 = new SubjectOption();
                option5.setCreateTime(new Date());
                option5.setOptionTitle("E");
                option5.setOptionContent(importDto.getOption_5());
                option5.setCorrectAble("E".indexOf(subject.getAnswer()) >= 0);
                option5.setSubjectId(subject.getSubjectId());
                options.add(option5);
            }

            if(StringUtils.isNotBlank(importDto.getOption_6())){
                SubjectOption option6 = new SubjectOption();
                option6.setCreateTime(new Date());
                option6.setOptionTitle("F");
                option6.setOptionContent(importDto.getOption_6());
                option6.setCorrectAble("F".indexOf(subject.getAnswer()) >= 0);
                option6.setSubjectId(subject.getSubjectId());
                options.add(option6);
            }
        }
        optionService.saveBatch(options);
        log.info("存储数据库成功！");
    }

    //Y/N转为A/B
    public String jduges2Letters(String option) {
        if (StringUtils.equals(option.toUpperCase(), "Y")) {
            return "A";
        } else if (StringUtils.equals(option.toUpperCase(), "N")) {
            return "B";
        } else {
            return option;
        }
    }

    //输入"1"或者"1/2" 通过调用convertToLetter返回"A"或"A/B"的方法
    public String numbers2Letters(String numbers) {
        String[] arr = numbers.split( StringPool.SLASH);
        List<String> letters = new ArrayList<>();
        for (String num : arr) {
            letters.add(convertToLetter(Integer.valueOf(num)));
        }
        return StringUtils.join(letters, StringPool.SLASH);
    }

    public String convertToLetter(int number) {
        if (number < 1 || number > 26) {
            throw new IllegalArgumentException("Number must be between 1 and 26");
        }
        return Character.toString((char) (number + 64));
    }

    @Override
    public List<Subject> findPageByFront(SubjectFilter subjectFilter) {
        QueryWrapper<Subject> wrapper = new QueryWrapper<Subject>();
        List<Subject> subjects = new ArrayList<>();
        wrapper.eq("delete_flag", 0L);
        if(StringUtils.isNotBlank(subjectFilter.getSubjectTitle())){
            wrapper.like("subject_title", subjectFilter.getSubjectTitle());
        }
        if(subjectFilter.getSubjectType() != null){
            wrapper.eq("subject_type", subjectFilter.getSubjectType());
        }
        //闯关模式 固定
        if(subjectFilter.getModuleType() != null){
            if(subjectFilter.getModuleType() == ModuleTypeEnum.COURSE.getCode()){
                wrapper.eq("module_type", ModuleTypeEnum.COURSE.getCode());
                wrapper.eq("scene_type", subjectFilter.getSceneType());
            }else if(subjectFilter.getModuleType() == ModuleTypeEnum.EXAM.getCode()){  //学员考试 随机10题
                wrapper.eq("module_type", ModuleTypeEnum.EXAM.getCode());
                wrapper.eq("personnel_type", subjectFilter.getPersonnelType());
            }else if(subjectFilter.getModuleType() == ModuleTypeEnum.EVLUATE.getCode()){
                wrapper.eq("module_type", ModuleTypeEnum.EVLUATE.getCode());
            }
            subjects = subjectMapper.selectList(wrapper);
        }else{
            subjects = list();
        }
        return subjects;
    }

    @Override
    public SubjectFrontVo findFrontBySubjectId(Long subjectId) {
        QueryWrapper wrapper = new QueryWrapper();
        wrapper.eq("subject_id", subjectId);
        Subject subject = subjectMapper.selectOne(wrapper);
        List<Subject> list = new ArrayList<>();
        list.add(subject);
        List<SubjectFrontVo> vos = assemblyFront(list);
        return vos.get(0);
    }

    @Override
    public EvluateSurveyVO findSurveyByFront(SubjectFilter subjectFilter) {
        EvluateSurvey publishedSurvey = surveyService.findPublishedPaper();

        if(publishedSurvey == null){
            throw new MyRuntimeException("没有已发布的问卷");
        }
        List<EvluateSurveyDetail> details = surveyDetailService.detail(publishedSurvey.getId());
        List<SubjectFrontVo> subjectFrontVos = new ArrayList<>();
        EvluateSurveyVO surveyVO = new EvluateSurveyVO();
        surveyVO.setSurveyId(publishedSurvey.getId());
        surveyVO.setSurveyName(publishedSurvey.getSurveyName());
        details.forEach(item -> {
            subjectFrontVos.add(JSONUtil.toBean(JSONUtil.toJsonStr(item.getSubjectContent()), SubjectFrontVo.class));
        });
        surveyVO.setSubjects(subjectFrontVos);
        return surveyVO;
    }

    @Override
    public void logicDelete(Long subjectId, Integer moduleType) {
        Subject subject = subjectMapper.selectBySubjectId(subjectId);
        if(subject == null){
            throw new MyRuntimeException("题目不存在");
        }

        //校验是否被试卷引用
        if(ModuleTypeEnum.EXAM.getCode() == moduleType){
            Long useCount = examPaperDetailService.subjectUseAble(subjectId);
            if(useCount > 0){
                throw new MyRuntimeException("题目已被试卷引用，不能删除");
            }
        }

        //校验是否被评价引用
        if(ModuleTypeEnum.EVLUATE.getCode() == moduleType){
            Long useCount = surveyDetailService.subjectUseAble(subjectId);
            if(useCount > 0){
                throw new MyRuntimeException("题目已被评价引用，不能删除");
            }
        }

        subject.setDeleteFlag(System.currentTimeMillis());
        subject.setUpdateTime(new Date());
        subjectMapper.updateById(subject);
    }
}
